#pragma once

#include <iostream>

#include "Error.h"
#include "StringUtils.h"

namespace TestFramework
{
    template <typename T> class BasicString;

    typedef BasicString<char> TestString;
    typedef BasicString<wchar_t> TestWString;

    template <typename T>
    class BasicString
    {
    public:
        typedef size_t SizeType;
        typedef T CharType;
        typedef CharType* Iterator;
        typedef const CharType* ConstIterator;
        static const SizeType NPos = -1;

    public:
        BasicString()
            : String(new CharType[1]())
            , Length(0)
        {
            String[0] = '\0';
        }

        BasicString(const CharType* value)
            : String(new CharType[GetStrLength(value) + 1]())
            , Length(GetStrLength(value))
        {
            Assign(value, value + Length, Begin());
        }

        BasicString(const CharType chr, SizeType count)
            : String(new CharType[count + 1]())
            , Length(count)
        {
            for (SizeType i = 0; i < count; ++i)
            {
                String[i] = chr;
            }

            String[count] = '\0';
        }

        BasicString(ConstIterator start, ConstIterator end)
            : String(new CharType[end - start + 1]())
            , Length(end - start)
        {
            Assign(start, end, Begin());
        }

        BasicString(const CharType* value, SizeType startIndex, SizeType length)
        {
            const SizeType strLength = GetStrLength(value);
            if ((startIndex <= strLength) && (length > 0))
            {
                if ((length + startIndex) >= strLength)
                {
                    length = strLength - startIndex;
                }

                String = new CharType[length + 1]();
                Assign(&value[startIndex], &value[startIndex + length], &String[0]);
                Length = length;
            }
            else
            {
                String = new CharType[1]();
                String[0] = '\0';
                Length = 0;
            }
        }

        BasicString(const BasicString& str)
            : String(new CharType[str.Length + 1]())
            , Length(str.Length)
        {
            Assign(str.Begin(), str.End(), Begin());
        }

        ~BasicString(void)
        {
            delete [] String;
        }

        friend std::ostream& operator<<(std::ostream& stream, const BasicString& inputStr)
        {
            stream << inputStr.String;
            return stream;
        }

        friend std::wostream& operator<<(std::wostream& stream, const BasicString& inputStr)
        {
            stream << inputStr.String;
            return stream;
        }

        friend std::istream& operator>>(std::istream& stream, BasicString& inputStr)
        {
            std::string tmp;
            stream >> tmp;
            inputStr.Clear();
            inputStr.Append(StrCopy(tmp.c_str()));
            return stream;
        }

        friend std::wistream& operator>>(std::wistream& stream, BasicString& inputStr)
        {
            std::wstring tmp;
            stream >> tmp;
            inputStr.Clear();
            inputStr.Append(StrCopy(tmp.c_str()));
            return stream;
        }

        SizeType GetLength() const
        {
            return Length;
        }

        BasicString Concat(const BasicString& lhs, const BasicString& rhs) const
        {
            return lhs + rhs;
        }

        BasicString SubString(ConstIterator start, ConstIterator end) const
        {
            BasicString tmp(start, end);
            return tmp;
        }

        BasicString operator+(const BasicString& inputStr) const
        {
            CharType* str = new CharType[Length + inputStr.Length + 1];
            Assign(Begin(), End(), &str[0]);
            Assign(inputStr.Begin(), inputStr.End(), &str[Length]);

            return BasicString(str);
        }

        bool operator<(const BasicString& rhs) const
        {
            ConstIterator iter = Begin();
            ConstIterator rIter = rhs.Begin();
            for (; iter != End(); ++iter, ++rIter)
            {
                if (rIter == rhs.End())
                {
                  return true;
                }

                if (*iter != *rIter)
                {
                    return *iter > *rIter;
                }
            }

            return false;
        }

        BasicString& operator=(const BasicString& inputStr)
        {
            if (this == &inputStr)
            {
                return *this;
            }

            const SizeType length = inputStr.GetLength();
            if (this->GetLength() != length)
            {
                delete [] String;
                String = new CharType[length + 1]();
            }

            Assign(inputStr.Begin(), inputStr.End(), Begin());
            Length = length;
            return *this;
        }

        bool operator==(const BasicString& rhs) const
        {
            if (GetLength()!= rhs.GetLength())
            {
                return false;
            }

            ConstIterator iter = Begin();
            ConstIterator iterRhs = rhs.Begin();
            for (; iter != End(); ++iter, ++iterRhs)
            {
                if (*iter != *iterRhs)
                {
                    return false;
                }
            }

            return true;
        }

        bool operator!=(const BasicString& rhs) const
        {
            return !(operator==(rhs));
        }

        BasicString& operator+=(const BasicString& inputStr)
        {
            const SizeType length = this->GetLength();
            const CharType* str = StrCopy(this->String);

            delete [] String;
            Length = length + inputStr.GetLength();
            String = new CharType[Length + 1]();
            Assign(&str[0], &str[0] + length, Begin());
            Assign(inputStr.Begin(), inputStr.End(), Begin() + length);
            delete [] str;

            return *this;
        }

        CharType& operator[](SizeType index)
        {
            CheckRange(index);
            return this->String[index];
        }

        BasicString& Append(const CharType* value)
        {
            BasicString tempStr(value);
            return *this += tempStr;
        }

        bool IsEmpty() const
        {
            return !Length;
        }

        void Clear()
        {
            delete [] this->String;
            this->String = new CharType[1]();
            String[0] = '\0';
            Length = 0;
        }

        SizeType Compare(const BasicString& inputStr) const
        {
            SizeType minLength =
                (inputStr.GetLength() > this->GetLength()) ? this->GetLength() : inputStr.GetLength();

            for (SizeType i = 0; i < minLength; ++i)
            {
                if (inputStr.String[i] < this->String[i])
                {
                    return -1;
                }
                else if (inputStr.String[i] > this->String[i])
                {
                    return 1;
                }
            }

            return 0;
        }

        SizeType Compare(const CharType* inputStr) const
        {
            BasicString tmp(inputStr);
            return Compare(tmp);
        }

        BasicString& Erase(SizeType pos, SizeType length)
        {
            const SizeType strLen = this->GetLength();
            if ((pos < strLen) && (length > 0))
            {
                SizeType len = length;
                if ((pos + len) > strLen)
                {
                    len = strLen - pos;
                }

                CharType* newStr = new CharType[strLen - len + 1]();
                Assign(Begin(), Begin() + pos, newStr);
                Assign(Begin() + pos + len, End(), newStr + pos);
                delete [] String;
                String = newStr;
                Length = strLen - len;
            }

            return *this;
        }

        const CharType* Data( ) const
        {
            return this->String;
        }

        BasicString& Insert(SizeType index, const BasicString& value)
        {
            CheckRange(index);
            const SizeType len = GetLength();
            const SizeType inputLen = value.GetLength();
            CharType* temp = new CharType[len + inputLen + 1]();
            Assign(Begin(), Begin() + index, temp);
            Assign(value.Begin(), value.End(), temp + index);
            Assign(Begin() + index, End(), temp + index + inputLen);

            delete [] String;
            String = temp;
            Length = len + inputLen;

            return *this;
        }

        BasicString& Insert(SizeType index, const CharType* value)
        {
            BasicString tValue(value);
            return Insert(index, tValue);
        }

        BasicString& Replace(SizeType pos, SizeType size, const BasicString& value)
        {
            SizeType len = GetLength();
            CheckRange(pos);
            for (SizeType i = 0; i < size; ++i)
            {
                if ((pos + i) >= len)
                {
                    break;
                }

                this->String[pos + i] = value.String[i];
            }

            this->String[len] = '\0';
            return *this;
        }

        BasicString& Replace(SizeType pos, SizeType size, const CharType* value)
        {
            BasicString tValue(value);
            return Replace(pos, size, tValue);
        }

        BasicString& Replace(ConstIterator startSource, ConstIterator endSource,
                             ConstIterator startTarget, ConstIterator endTarget)
        {
            if ((Begin() > startSource) || (End() <= startSource) || (End() < endSource) || (Begin() > endSource))
            {
                throw std::exception("Wrong input iterator parameter!", Errors::WRONG_INPUT_PARAMETER);
            }

            SizeType size = (endTarget - startTarget) - (endSource - startSource) + GetLength() + 1;
            CharType* str = new CharType[size]();
            Assign(Begin(), startSource, str);
            Assign(startTarget, endTarget, str + (startSource - Begin()));
            Assign(endSource, End(), str + (startSource - Begin()) + (endTarget - startTarget));
            delete [] String;
            String = str;
            Length = GetStrLength(String);
            return *this;
        }

        void Swap(BasicString& inputStr)
        {
            if (this != &inputStr)
            {
              const BasicString tmp(*this);
              *this = inputStr;
              Length = inputStr.Length;
              inputStr = tmp;
            }
        }

        SizeType Find(const BasicString& inputStr) const
        {
            if (inputStr.GetLength() && inputStr.GetLength() > GetLength())
            {
                return NPos;
            }

            ConstIterator iter = Begin();
            ConstIterator inputIter = inputStr.Begin();
            ConstIterator found = End();
            for (; (iter != End()) && (inputIter != inputStr.End()); ++iter)
            {
                if (*iter == *inputIter)
                {
                    ++inputIter;
                    if (found == End())
                    {
                        found = iter;
                    }
                }
                else
                {
                    found = End();
                }
            }

            return (iter == End() && inputIter != inputStr.End()) ? NPos : found - Begin();
        }

        SizeType RFind(const BasicString& inputStr) const
        {
            if (inputStr.GetLength() && inputStr.GetLength() > GetLength())
            {
                return NPos;
            }

            ConstIterator iter = End() - 1;
            ConstIterator inputIter = inputStr.End() - 1;
            SizeType found = NPos;
            for (; (iter != Begin()); --iter)
            {
                if (*iter == *inputIter)
                {
                    if (inputIter == inputStr.Begin())
                    {
                        return iter - Begin();
                    }

                    --inputIter;
                }
                else
                {
                    found = NPos;
                    inputIter = inputStr.End() - 1;
                }
            }

            return found;
        }

        Iterator Begin()
        {
            return &String[0];
        }

        ConstIterator Begin() const
        {
            return &String[0];
        }

        Iterator End()
        {
            return &String[GetLength()];
        }

        ConstIterator End() const
        {
            return &String[GetLength()];
        }
        
    private:
        void CheckRange(SizeType index) const
        {
            if (index >= GetLength())
            {
                throw std::exception("Out of range!", Errors::ERROR_OUT_OF_RANGE);
            }
        }

        void Assign(ConstIterator sourceBegin, ConstIterator sourceEnd, Iterator targetBegin) const
        {
            for (; sourceBegin != sourceEnd; ++sourceBegin, ++targetBegin)
            {
                *targetBegin = *sourceBegin;
            }

            *targetBegin = '\0';
        }

    private:
        CharType* String;
        SizeType Length;
    };
}
